Imports System.Xml
Imports System.IO
Imports System.text
Imports System.Web

Public Class CMPDatabase
    Private Const Q As String = Chr(34)

    Private msSourceDatabaseServer As String
    Private msSourceDatabaseName As String
    Private mlSourceServerID As Long
    Private mlSourceDatabaseID As Long
    Private mlSourceDatabaseRevID As Long

    Private msTargetDatabaseServer As String
    Private msTargetDatabaseName As String
    Private mlTargetServerID As Long
    Private mlTargetDatabaseID As Long
    Private mlTargetDatabaseRevID As Long

    Private moSourceXML As New XmlDocument
    Private moTargetXML As New XmlDocument

    Private moTables As New SortedList
    Private moViews As New SortedList
    Private moFunctions As New SortedList
    Private moSProcs As New SortedList

    Private Enum SourceType As Integer
        Source = 1
        Target = 2
    End Enum

    Public ReadOnly Property Tables() As Collection
        Get
            Return moTables.Values
        End Get
    End Property

    Public ReadOnly Property Views() As Collection
        Get
            Return moViews.Values
        End Get
    End Property

    Public ReadOnly Property Functions() As Collection
        Get
            Return moViews.Values
        End Get
    End Property

    Public ReadOnly Property SProcs() As Collection
        Get
            Return moSProcs.Values
        End Get
    End Property

    Public Sub Compare( _
        ByVal sSourceDBXML As String, _
        ByVal sTargetDBXML As String)

        moSourceXML.LoadXml(sSourceDBXML)
        moTargetXML.LoadXml(sTargetDBXML)

        '---Get database IDs
        Dim oXMLNode As XmlNode
        oXMLNode = moSourceXML.SelectSingleNode("database")
        msSourceDatabaseServer = oXMLNode.Attributes("ServerName").Value
        msSourceDatabaseName = oXMLNode.Attributes("name").Value
        mlSourceServerID = oXMLNode.Attributes("ServerID").Value
        mlSourceDatabaseID = oXMLNode.Attributes("id").Value
        mlSourceDatabaseRevID = oXMLNode.Attributes("rev").Value

        oXMLNode = moTargetXML.SelectSingleNode("database")
        msTargetDatabaseServer = oXMLNode.Attributes("ServerName").Value
        msTargetDatabaseName = oXMLNode.Attributes("name").Value
        mlTargetServerID = oXMLNode.Attributes("ServerID").Value
        mlTargetDatabaseID = oXMLNode.Attributes("id").Value
        mlTargetDatabaseRevID = oXMLNode.Attributes("rev").Value

        '---Build unique list of tables
        BuildTableList(moSourceXML, SourceType.Source)
        BuildTableList(moTargetXML, SourceType.Target)
        CompareObjectDates(moTables)

        '---Build unique list of views
        BuildViewList(moSourceXML, SourceType.Source)
        BuildViewList(moTargetXML, SourceType.Target)
        CompareObjectDates(moViews)

        '---Build unique list of functions
        BuildFunctionList(moSourceXML, SourceType.Source)
        BuildFunctionList(moTargetXML, SourceType.Target)
        CompareObjectDates(moFunctions)

        '---Build unique list of stored procedures
        BuildSProcList(moSourceXML, SourceType.Source)
        BuildSProcList(moTargetXML, SourceType.Target)
        CompareObjectDates(moSProcs)
    End Sub

    Private Sub BuildTableList( _
                    ByRef oDoc As XmlDocument, _
                    ByVal iSource As SourceType)
        '---Gets a list of tables in a database XML definition.
        '   If they don't already exist in the list then they
        '   are added, otherwise they are updated.

        Dim oNodeList As XmlNodeList = oDoc.SelectNodes("/database/dbtables/table")

        For Each oNode As XmlNode In oNodeList
            Dim oTable As CMPTable = Nothing

            If moTables.Contains(oNode.Attributes("owner").Value & "." & oNode.Attributes("name").Value) Then
                '--Update
                oTable = moTables(oNode.Attributes("owner").Value & "." & oNode.Attributes("name").Value)
            Else
                '--Add
                oTable = New CMPTable
                With oTable
                    .ObjectName = oNode.Attributes("name").Value
                    .OwnerName = oNode.Attributes("owner").Value

                    .SourceServerID = mlSourceServerID
                    .SourceDatabaseID = mlSourceDatabaseID
                    .SourceDatabaseRevID = mlSourceDatabaseRevID

                    .TargetServerID = mlTargetServerID
                    .TargetDatabaseID = mlTargetDatabaseID
                    .TargetDatabaseRevID = mlTargetDatabaseRevID

                    moTables.Add(.OwnerName & "." & .ObjectName, oTable)
                End With
            End If

            With otable
                .SourceAndTargetEqual = False

                Select Case iSource
                    Case SourceType.Source
                        .SourceObjectID = oNode.Attributes("id").Value
                        .SourceObjectRevID = oNode.Attributes("rev").Value
                        .SourceLastModified = oNode.Attributes("createdate").Value
                        .SourceHash = GetTableHash(oDoc, .SourceObjectID)

                    Case SourceType.Target
                        .TargetObjectID = oNode.Attributes("id").Value
                        .TargetObjectRevID = oNode.Attributes("rev").Value
                        .TargetLastModified = oNode.Attributes("createdate").Value
                        .TargetHash = GetTableHash(oDoc, .TargetObjectID)
                End Select
            End With
        Next
    End Sub

    Private Sub BuildViewList( _
                ByRef oDoc As XmlDocument, _
                ByVal iSource As SourceType)
        '---Gets a list of tables in a database XML definition.
        '   If they don't already exist in the list then they
        '   are added, otherwise they are updated.

        Dim oNodeList As XmlNodeList = oDoc.SelectNodes("/database/dbviews/view")

        For Each oNode As XmlNode In oNodeList
            Dim oView As CMPView = Nothing

            If moViews.Contains(oNode.Attributes("owner").Value & "." & oNode.Attributes("name").Value) Then
                '--Update
                oView = moViews(oNode.Attributes("owner").Value & "." & oNode.Attributes("name").Value)
            Else
                '--Add
                oView = New CMPView
                With oView
                    .ObjectName = oNode.Attributes("name").Value
                    .OwnerName = oNode.Attributes("owner").Value

                    .SourceServerID = mlSourceServerID
                    .SourceDatabaseID = mlSourceDatabaseID
                    .SourceDatabaseRevID = mlSourceDatabaseRevID

                    .TargetServerID = mlTargetServerID
                    .TargetDatabaseID = mlTargetDatabaseID
                    .TargetDatabaseRevID = mlTargetDatabaseRevID

                    moViews.Add(.OwnerName & "." & .ObjectName, oView)
                End With
            End If

            With oView
                .SourceAndTargetEqual = False

                Select Case iSource
                    Case SourceType.Source
                        .SourceObjectID = oNode.Attributes("id").Value
                        .SourceObjectRevID = oNode.Attributes("rev").Value
                        .SourceLastModified = oNode.Attributes("createdate").Value
                        .SourceHash = GetViewHash(oDoc, .SourceObjectID)

                    Case SourceType.Target
                        .TargetObjectID = oNode.Attributes("id").Value
                        .TargetObjectRevID = oNode.Attributes("rev").Value
                        .TargetLastModified = oNode.Attributes("createdate").Value
                        .TargetHash = GetViewHash(oDoc, .TargetObjectID)
                End Select
            End With
        Next
    End Sub

    Private Sub BuildFunctionList( _
            ByRef oDoc As XmlDocument, _
            ByVal iSource As SourceType)
        '---Gets a list of functions in a database XML definition.
        '   If they don't already exist in the list then they
        '   are added, otherwise they are updated.

        Dim oNodeList As XmlNodeList = oDoc.SelectNodes("/database/dbfunctions/function")

        For Each oNode As XmlNode In oNodeList
            Dim oFunction As CMPfunction = Nothing

            If moFunctions.Contains(oNode.Attributes("owner").Value & "." & oNode.Attributes("name").Value) Then
                '--Update
                oFunction = moFunctions(oNode.Attributes("owner").Value & "." & oNode.Attributes("name").Value)
            Else
                '--Add
                oFunction = New CMPFunction
                With oFunction
                    .ObjectName = oNode.Attributes("name").Value
                    .OwnerName = oNode.Attributes("owner").Value

                    .SourceServerID = mlSourceServerID
                    .SourceDatabaseID = mlSourceDatabaseID
                    .SourceDatabaseRevID = mlSourceDatabaseRevID

                    .TargetServerID = mlTargetServerID
                    .TargetDatabaseID = mlTargetDatabaseID
                    .TargetDatabaseRevID = mlTargetDatabaseRevID

                    moFunctions.Add(.OwnerName & "." & .ObjectName, oFunction)
                End With
            End If

            With oFunction
                .SourceAndTargetEqual = False

                Select Case iSource
                    Case SourceType.Source
                        .SourceObjectID = oNode.Attributes("id").Value
                        .SourceObjectRevID = oNode.Attributes("rev").Value
                        .SourceLastModified = oNode.Attributes("createdate").Value
                        .SourceHash = GetFunctionHash(oDoc, .SourceObjectID)

                    Case SourceType.Target
                        .TargetObjectID = oNode.Attributes("id").Value
                        .TargetObjectRevID = oNode.Attributes("rev").Value
                        .TargetLastModified = oNode.Attributes("createdate").Value
                        .TargetHash = GetFunctionHash(oDoc, .TargetObjectID)
                End Select
            End With
        Next
    End Sub

    Private Sub BuildSProcList( _
                ByRef oDoc As XmlDocument, _
                ByVal iSource As SourceType)
        '---Gets a list of tables in a database XML definition.
        '   If they don't already exist in the list then they
        '   are added, otherwise they are updated.

        Dim oNodeList As XmlNodeList = oDoc.SelectNodes("/database/dbprocedures/procedure")

        For Each oNode As XmlNode In oNodeList
            Dim oSProc As CMPSProc = Nothing

            If moSProcs.Contains(oNode.Attributes("owner").Value & "." & oNode.Attributes("name").Value) Then
                '--Update
                oSProc = moSProcs(oNode.Attributes("owner").Value & "." & oNode.Attributes("name").Value)
            Else
                '--Add
                oSProc = New CMPSProc
                With oSProc
                    .ObjectName = oNode.Attributes("name").Value
                    .OwnerName = oNode.Attributes("owner").Value

                    .SourceServerID = mlSourceServerID
                    .SourceDatabaseID = mlSourceDatabaseID
                    .SourceDatabaseRevID = mlSourceDatabaseRevID

                    .TargetServerID = mlTargetServerID
                    .TargetDatabaseID = mlTargetDatabaseID
                    .TargetDatabaseRevID = mlTargetDatabaseRevID

                    moSProcs.Add(.OwnerName & "." & .ObjectName, oSProc)
                End With
            End If

            With oSProc
                .SourceAndTargetEqual = False

                Select Case iSource
                    Case SourceType.Source
                        .SourceObjectID = oNode.Attributes("id").Value
                        .SourceObjectRevID = oNode.Attributes("rev").Value
                        .SourceLastModified = oNode.Attributes("createdate").Value
                        .SourceHash = GetSProcHash(oDoc, .SourceObjectID)

                    Case SourceType.Target
                        .TargetObjectID = oNode.Attributes("id").Value
                        .TargetObjectRevID = oNode.Attributes("rev").Value
                        .TargetLastModified = oNode.Attributes("createdate").Value
                        .TargetHash = GetSProcHash(oDoc, .TargetObjectID)
                End Select
            End With
        Next
    End Sub

    Private Function GetTableHash( _
                        ByRef oDoc As XmlDocument, _
                        ByVal lID As Long) As String

        '---Build text snippet representing relevant data for the fields of the table.
        Dim sText As String = "<fields>"
        Dim oValue As XmlNode

        For Each oNode As XmlNode In oDoc.SelectNodes("database/dbtables/table[@id=" & lID & "]/fields/field")
            sText = sText & "<field>"

            oValue = oNode.SelectSingleNode("Name")
            sText = sText & "<v>" & oValue.InnerText.ToLower & "</v>"

            oValue = oNode.SelectSingleNode("Type")
            sText = sText & "<v>" & oValue.InnerText.ToLower & "</v>"

            oValue = oNode.SelectSingleNode("Size")
            sText = sText & "<v>" & oValue.InnerText.ToLower & "</v>"

            oValue = oNode.SelectSingleNode("Scale")
            sText = sText & "<v>" & oValue.InnerText.ToLower & "</v>"

            oValue = oNode.SelectSingleNode("Precision")
            sText = sText & "<v>" & oValue.InnerText.ToLower & "</v>"

            oValue = oNode.SelectSingleNode("ReferencesFK")
            sText = sText & "<v>" & oValue.InnerText.ToLower & "</v>"

            oValue = oNode.SelectSingleNode("IsNotNull")
            sText = sText & "<v>" & oValue.InnerText.ToLower & "</v>"

            oValue = oNode.SelectSingleNode("IsIdentity")
            sText = sText & "<v>" & oValue.InnerText.ToLower & "</v>"

            oValue = oNode.SelectSingleNode("DefaultValue")
            sText = sText & "<v>" & oValue.InnerText.ToLower & "</v>"

            oValue = oNode.SelectSingleNode("Constraint")
            sText = sText & "<v>" & oValue.InnerText.ToLower & "</v>"

            sText = sText & "</field>"
        Next

        sText = sText & "</fields>"

        sText = sText & "<indexes>"

        sText = sText & "</indexes>"

        Return ComputeSHA1(sText)
    End Function

    Private Function GetViewHash( _
                        ByRef oDoc As XmlDocument, _
                        ByVal lID As Long) As String

        '---Build text snippet representing relevant data for the fields of the view.
        Dim sText As String = "<views>"

        Dim oNode As XmlNode = oDoc.SelectSingleNode("database/dbviews/view[@id=" & lID & "]/vtext")
        sText = sText & "<view>"

        sText = sText & "<v>" & oNode.InnerText.ToLower & "</v>"

        sText = sText & "</view>"

        sText = sText & "</views>"

        Return ComputeSHA1(sText)
    End Function

    Private Function GetFunctionHash( _
                    ByRef oDoc As XmlDocument, _
                    ByVal lID As Long) As String

        '---Build text snippet representing relevant data for the fields of the view.
        Dim sText As String = "<functions>"

        Dim oNode As XmlNode = oDoc.SelectSingleNode("database/dbfunctions/function[@id=" & lID & "]/ftext")
        sText = sText & "<function>"

        sText = sText & "<f>" & oNode.InnerText.ToLower & "</f>"

        sText = sText & "</function>"

        sText = sText & "</functions>"

        Return ComputeSHA1(sText)
    End Function

    Private Function GetSProcHash( _
                        ByRef oDoc As XmlDocument, _
                        ByVal lID As Long) As String

        '---Build text snippet representing relevant data for the fields of the sproc.
        Dim sText As String = "<sprocs>"

        Dim oNode As XmlNode = oDoc.SelectSingleNode("database/dbprocedures/procedure[@id=" & lID & "]/ptext")
        sText = sText & "<sproc>"

        sText = sText & "<p>" & oNode.InnerText.ToLower & "</p>"

        sText = sText & "</sproc>"

        sText = sText & "</sprocs>"

        Return ComputeSHA1(sText)
    End Function

    Private Sub CompareObjectDates(ByRef oList As SortedList)
        For Each oObject As CMPBase In oList.Values
            With oObject
                If .TargetHash = .SourceHash Then
                    .SourceAndTargetEqual = True

                ElseIf IsDate(.SourceLastModified) AndAlso IsDate(.TargetLastModified) Then
                    If DateDiff(DateInterval.Second, CDate(.SourceLastModified), CDate(.TargetLastModified)) < 0 Then
                        .SourceNewer = True
                    Else
                        .TargetNewer = True
                    End If

                ElseIf Not IsDate(.SourceLastModified) Then
                    .TargetNewer = True

                ElseIf Not IsDate(.TargetLastModified) Then
                    .SourceNewer = True
                End If
            End With
        Next
    End Sub

    Private Function ComputeSHA1(ByVal sText As String) As String
        Dim sAllFields As String = ""

        Dim oCrypto As New JCO
        Return oCrypto.GetHash(sText)
    End Function

    Public Function ToXML() As String
        Dim oDoc As New XmlDocument

        Dim oMS As New MemoryStream
        Dim oXMLW As New XmlTextWriter(oMS, Encoding.UTF8)
        Dim oOS As New StreamReader(oMS)

        oXMLW.WriteStartDocument()
        oXMLW.WriteProcessingInstruction("xml-stylesheet", "type=" & Q & "text/xsl" & Q & " href=" & Q & "http://" & HttpContext.Current.Session("BASEDIR") & "/sad/d1.xsl" & Q)

        oXMLW.WriteStartElement("sadcompare")

        oXMLW.WriteStartElement("database")

        oXMLW.WriteStartElement("sourcedb")

        oXMLW.WriteStartAttribute("servername")
        oXMLW.WriteValue(msSourceDatabaseServer)
        oXMLW.WriteEndAttribute()

        oXMLW.WriteStartAttribute("serverid")
        oXMLW.WriteValue(mlSourceServerID)
        oXMLW.WriteEndAttribute()

        oXMLW.WriteStartAttribute("databasename")
        oXMLW.WriteValue(msSourceDatabaseName)
        oXMLW.WriteEndAttribute()

        oXMLW.WriteStartAttribute("databaseid")
        oXMLW.WriteValue(mlSourceDatabaseID)
        oXMLW.WriteEndAttribute()

        oXMLW.WriteStartAttribute("databaserevid")
        oXMLW.WriteValue(mlSourceDatabaseRevID)
        oXMLW.WriteEndAttribute()

        oXMLW.WriteEndElement()

        oXMLW.WriteStartElement("targetdb")

        oXMLW.WriteStartAttribute("servername")
        oXMLW.WriteValue(msTargetDatabaseServer)
        oXMLW.WriteEndAttribute()

        oXMLW.WriteStartAttribute("serverid")
        oXMLW.WriteValue(mlTargetServerID)
        oXMLW.WriteEndAttribute()

        oXMLW.WriteStartAttribute("databasename")
        oXMLW.WriteValue(msTargetDatabaseName)
        oXMLW.WriteEndAttribute()

        oXMLW.WriteStartAttribute("databaseid")
        oXMLW.WriteValue(mlTargetDatabaseID)
        oXMLW.WriteEndAttribute()

        oXMLW.WriteStartAttribute("databaserevid")
        oXMLW.WriteValue(mlTargetDatabaseRevID)
        oXMLW.WriteEndAttribute()

        oXMLW.WriteEndElement()

        oXMLW.WriteEndElement() 'database

        '---Tables
        oXMLW.WriteStartElement("tables")
        For Each oTable As SQLAutoDocLib.CMPTable In moTables.Values
            oXMLW.WriteStartElement("table")

            With oTable
                oXMLW.WriteStartAttribute("name")
                oXMLW.WriteValue(.ObjectName)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("owner")
                oXMLW.WriteValue(.OwnerName)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("sourceandtargetequal")
                oXMLW.WriteValue(.SourceAndTargetEqual)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("sourcenewer")
                oXMLW.WriteValue(.SourceNewer)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("targetnewer")
                oXMLW.WriteValue(.TargetNewer)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartElement("source")

                oXMLW.WriteStartAttribute("id")
                oXMLW.WriteValue(.SourceObjectID)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("rev")
                oXMLW.WriteValue(.SourceObjectRevID)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("lastmodified")
                oXMLW.WriteValue(.SourceLastModified)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteEndElement() 'source

                oXMLW.WriteStartElement("target")

                oXMLW.WriteStartAttribute("id")
                oXMLW.WriteValue(.TargetObjectID)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("rev")
                oXMLW.WriteValue(.TargetObjectRevID)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("lastmodified")
                oXMLW.WriteValue(.TargetLastModified)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteEndElement() 'target
            End With

            oXMLW.WriteEndElement() 'table
        Next
        oXMLW.WriteEndElement() 'tables

        '---Views
        oXMLW.WriteStartElement("views")
        For Each oView As SQLAutoDocLib.CMPView In moViews.Values
            oXMLW.WriteStartElement("view")

            With oView
                oXMLW.WriteStartAttribute("name")
                oXMLW.WriteValue(.ObjectName)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("owner")
                oXMLW.WriteValue(.OwnerName)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("sourceandtargetequal")
                oXMLW.WriteValue(.SourceAndTargetEqual)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("sourcenewer")
                oXMLW.WriteValue(.SourceNewer)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("targetnewer")
                oXMLW.WriteValue(.TargetNewer)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartElement("source")

                oXMLW.WriteStartAttribute("id")
                oXMLW.WriteValue(.SourceObjectID)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("rev")
                oXMLW.WriteValue(.SourceObjectRevID)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("lastmodified")
                oXMLW.WriteValue(.SourceLastModified)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteEndElement() 'source

                oXMLW.WriteStartElement("target")

                oXMLW.WriteStartAttribute("id")
                oXMLW.WriteValue(.TargetObjectID)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("rev")
                oXMLW.WriteValue(.TargetObjectRevID)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("lastmodified")
                oXMLW.WriteValue(.TargetLastModified)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteEndElement() 'target
            End With

            oXMLW.WriteEndElement() 'table
        Next
        oXMLW.WriteEndElement() 'views

        '---Functions
        oXMLW.WriteStartElement("functions")
        For Each oFunction As SQLAutoDocLib.CMPFunction In moFunctions.Values
            oXMLW.WriteStartElement("function")

            With oFunction
                oXMLW.WriteStartAttribute("name")
                oXMLW.WriteValue(.ObjectName)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("owner")
                oXMLW.WriteValue(.OwnerName)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("sourceandtargetequal")
                oXMLW.WriteValue(.SourceAndTargetEqual)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("sourcenewer")
                oXMLW.WriteValue(.SourceNewer)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("targetnewer")
                oXMLW.WriteValue(.TargetNewer)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartElement("source")

                oXMLW.WriteStartAttribute("id")
                oXMLW.WriteValue(.SourceObjectID)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("rev")
                oXMLW.WriteValue(.SourceObjectRevID)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("lastmodified")
                oXMLW.WriteValue(.SourceLastModified)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteEndElement() 'source

                oXMLW.WriteStartElement("target")

                oXMLW.WriteStartAttribute("id")
                oXMLW.WriteValue(.TargetObjectID)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("rev")
                oXMLW.WriteValue(.TargetObjectRevID)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("lastmodified")
                oXMLW.WriteValue(.TargetLastModified)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteEndElement() 'target
            End With

            oXMLW.WriteEndElement() 'table
        Next
        oXMLW.WriteEndElement() 'functions

        '---Stored Procedures
        oXMLW.WriteStartElement("procedures")
        For Each oSProc As SQLAutoDocLib.CMPSProc In moSProcs.Values
            oXMLW.WriteStartElement("procedure")

            With oSProc
                oXMLW.WriteStartAttribute("name")
                oXMLW.WriteValue(.ObjectName)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("owner")
                oXMLW.WriteValue(.OwnerName)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("sourceandtargetequal")
                oXMLW.WriteValue(.SourceAndTargetEqual)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("sourcenewer")
                oXMLW.WriteValue(.SourceNewer)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("targetnewer")
                oXMLW.WriteValue(.TargetNewer)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartElement("source")

                oXMLW.WriteStartAttribute("id")
                oXMLW.WriteValue(.SourceObjectID)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("rev")
                oXMLW.WriteValue(.SourceObjectRevID)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("lastmodified")
                oXMLW.WriteValue(.SourceLastModified)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteEndElement() 'source

                oXMLW.WriteStartElement("target")

                oXMLW.WriteStartAttribute("id")
                oXMLW.WriteValue(.TargetObjectID)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("rev")
                oXMLW.WriteValue(.TargetObjectRevID)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteStartAttribute("lastmodified")
                oXMLW.WriteValue(.TargetLastModified)
                oXMLW.WriteEndAttribute()

                oXMLW.WriteEndElement() 'target
            End With

            oXMLW.WriteEndElement() 'table
        Next
        oXMLW.WriteEndElement() 'procedures

        oXMLW.WriteEndElement() 'sadcompare

        oXMLW.WriteEndDocument()    'Document

        oXMLW.Flush()

        Dim oEncoding As System.Text.UTF8Encoding = New System.Text.UTF8Encoding
        Dim sXML As String = oEncoding.GetString(oMS.GetBuffer)

        Dim iLastNULL As Integer = sXML.IndexOf(Chr(0)) - 1
        If iLastNULL = -1 Then iLastNULL = sXML.Length - 1

        Return sXML.Substring(1, iLastNULL)
    End Function

End Class
